/*
 * Copyright (C) 2006-2007 Eskil Bylund
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

using System;
using System.Collections.Generic;

using Gtk;
using Mono.Unix;

using DCSharp.Backend.Managers;
using DCSharp.Backend.Objects;
using DCSharp.Xml;

namespace DCSharp.Gui
{
	public class DownloadStore : TreeStore
	{
		private Dictionary<string, TreeIter> directories;
		private Dictionary<FileDownloader, TreeIter> downloads;

		public enum Column {
			Object,
			Name,
			Path,
			Size,
			Bytes,
			DownloadedBytes,
			Downloaded,
			Progress,
			TimeLeft,
			Loaded
		};

		public DownloadStore() : base(typeof(object), typeof(string),
			typeof(string), typeof(string), typeof(double), typeof(double),
			typeof(string), typeof(int), typeof(string), typeof(bool))
		{
			directories = new Dictionary<string, TreeIter>();
			downloads = new Dictionary<FileDownloader, TreeIter>();
		}

		#region Methods

		public void Add(FileDownloader downloader)
		{
			if (downloads.ContainsKey(downloader))
			{
				return;
			}

			TreeIter parent = CreateDirectoryTree(downloader.SavePath);
			TreeIter iter = AppendValues(parent, downloader, downloader.RemoteFile.Name);

			downloads.Add(downloader, iter);

			UpdateDownload(downloader, iter);
			UpdateParents(parent);
		}

		public void Remove(FileDownloader downloader)
		{
			if (!downloads.ContainsKey(downloader))
			{
				return;
			}

			TreeIter iter = downloads[downloader];
			downloads.Remove(downloader);

			TreeIter parent;
			if (IterParent(out parent, iter))
			{
				Remove(ref iter);
				UpdateParents(parent);

				RemoveEmptyDirectories(parent);
			}
			else
			{
				Remove(ref iter);
			}
		}

		public bool Contains(FileDownloader downloader)
		{
			return downloads.ContainsKey(downloader);
		}

		public TreeIter GetIter(FileDownloader downloader)
		{
			return downloads[downloader];
		}

		private TreeIter CreateDirectoryTree(string path)
		{
			string root;
			string name;

			if (path == Globals.Runtime.Settings.DownloadDirectory ||
				path.StartsWith(Globals.Runtime.Settings.DownloadDirectory))
			{
				root = Globals.Runtime.Settings.DownloadDirectory;
				name = Catalog.GetString("Download folder");
			}
			else
			{
				root = System.IO.Path.GetPathRoot(path);
				if (root == "/")
				{
					name = Catalog.GetString("Filesystem");
				}
				else
				{
					name = root;
				}
			}

			if (root != null)
			{
				if (!directories.ContainsKey(root))
				{
					TreeIter iter = AppendValues(null, name, root);
					directories.Add(root, iter);
				}

				path = path.Substring(root.Length);
				if (path == null)
				{
					return directories[root];
				}
			}

			string parent = root;
			string[] dirs = path.Split(System.IO.Path.DirectorySeparatorChar);
			foreach (string dir in dirs)
			{
				path = System.IO.Path.Combine(parent, dir);
				if (!directories.ContainsKey(path))
				{
					if (parent != null)
					{
						TreeIter parentIter = directories[parent];
						TreeIter iter = AppendValues(parentIter, null, dir, path);

						directories.Add(path, iter);
					}
					else
					{
						TreeIter iter = AppendValues(null, dir, path);

						directories.Add(path, iter);
					}
				}
				parent = path;
			}

			return directories[path];
		}

		private void RemoveEmptyDirectories(TreeIter iter)
		{
			if (!IterHasChild(iter))
			{
				string path = GetValue(iter, (int)Column.Path) as string;
				directories.Remove(path);

				TreeIter parent;
				if (IterParent(out parent, iter))
				{
					Remove(ref iter);
					RemoveEmptyDirectories(parent);
				}
				else
				{
					Remove(ref iter);
				}
			}
		}

		public void UpdateDownload(FileDownloader downloader)
		{
			TreeIter iter;
			if (downloads.TryGetValue(downloader, out iter))
			{
				UpdateDownload(downloader, iter);
			}
		}

		public void UpdateDownload(TreeIter iter)
		{
			FileDownloader download = GetValue(iter,
				(int)Column.Object) as FileDownloader;

			if (download != null)
			{
				UpdateDownload(download, iter);
			}
		}

		private void UpdateDownload(FileDownloader downloader, TreeIter iter)
		{
			RemoteFileInfo remoteFile = downloader.RemoteFile;
			double downloaded = downloader.Progress * remoteFile.Size;

			SetValue(iter, (int)Column.Name, remoteFile.Name);
			SetValue(iter, (int)Column.Size, Util.FormatFileSize(remoteFile.Size));
			SetValue(iter, (int)Column.Bytes, (double)remoteFile.Size);
			SetValue(iter, (int)Column.DownloadedBytes, downloaded);

			// Progress
			SetValue(iter, (int)Column.Progress, (int)(downloader.Progress * 100));

			// Downloaded
			string progress = String.Format(Catalog.GetString("{0} of {1}"),
				Util.FormatFileSize((long)downloaded),
				Util.FormatFileSize(remoteFile.Size));

			SetValue(iter, (int)Column.Downloaded, " " + progress + " ");

			// Time left
			if (downloader.State == DownloadState.Active)
			{
				TimeSpan remaining = downloader.GetRemainingTime();
				SetValue(iter, (int)Column.TimeLeft, remaining.ToString());
			}
			else
			{
				SetValue(iter, (int)Column.TimeLeft, null);
			}
		}

		public void UpdateDirectory(TreeIter iter)
		{
			string path = (string)GetValue(iter, (int)Column.Path);
			if (path != null)
			{
				// It's a directory
				double bytes = 0;
				double downloadedBytes = 0;
				double progress = 0;

				TreeIter childIter;
				if (IterChildren(out childIter, iter))
				{
					do
					{
						bytes += (double)GetValue(childIter, (int)Column.Bytes);
						downloadedBytes += (double)GetValue(childIter,
							(int)Column.DownloadedBytes);
					}
					while (IterNext(ref childIter));
				}
				progress = bytes > 0 ? downloadedBytes / bytes : 0;

				int children = IterNChildren(iter);
				SetValue(iter, (int)Column.Size, String.Format(
					Catalog.GetPluralString("{0} object", "{0} objects", children),
					children));

				SetValue(iter, (int)Column.Bytes, bytes);
				SetValue(iter, (int)Column.DownloadedBytes, downloadedBytes);
				SetValue(iter, (int)Column.Progress, (int)(progress * 100));

				// Downloaded
				string downloaded = String.Format(Catalog.GetString("{0} of {1}"),
					Util.FormatFileSize((long)downloadedBytes),
					Util.FormatFileSize((long)bytes));

				SetValue(iter, (int)Column.Downloaded, " " + downloaded + " ");
			}
		}

		private void UpdateParents(TreeIter iter)
		{
			do
			{
				UpdateDirectory(iter);
			}
			while (IterParent(out iter, iter));
		}

		#endregion
	}
}
